﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;

namespace CodeContrib.Data.Entity
{
	public class FilteredDbSet<TEntity> : IDbSet<TEntity>, IOrderedQueryable<TEntity>, IListSource where TEntity : class
	{
		private readonly Expression<Func<TEntity, bool>> _filter;
		private readonly Action<TEntity> _initializeEntity;
		private readonly Func<TEntity, bool> _matchesFilter;
		private readonly DbSet<TEntity> _set;

		bool IListSource.ContainsListCollection
		{
			get { return false; }
		}

		Type IQueryable.ElementType
		{
			get { return typeof (TEntity); }
		}

		Expression IQueryable.Expression
		{
			get { return Filtered.Expression; }
		}

		private IQueryable<TEntity> Filtered
		{
			get { return _set.Where(_filter); }
		}

		public ObservableCollection<TEntity> Local
		{
			get { return _set.Local; }
		}

		public Func<TEntity, bool> MatchesFilter
		{
			get { return _matchesFilter; }
		}

		IQueryProvider IQueryable.Provider
		{
			get { return Filtered.Provider; }
		}

		public FilteredDbSet(DbContext context) : this(context.Set<TEntity>(), i => true, null)
		{
		}

		public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter) : this(context.Set<TEntity>(), filter, null)
		{
		}

		public FilteredDbSet(DbContext context, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity) : this(context.Set<TEntity>(), filter, initializeEntity)
		{
		}

		private FilteredDbSet(DbSet<TEntity> set, Expression<Func<TEntity, bool>> filter, Action<TEntity> initializeEntity)
		{
			_initializeEntity = initializeEntity;
			_filter = filter;
			_matchesFilter = filter.Compile();
			_set = set;
		}

		public TEntity Add(TEntity entity)
		{
			InitializeEntity(entity);
			ThrowIfEntityDoesNotMatchFilter(entity);

			return _set.Add(entity);
		}

		public TEntity Attach(TEntity entity)
		{
			ThrowIfEntityDoesNotMatchFilter(entity);

			return _set.Attach(entity);
		}

		public TDerivedEntity Create<TDerivedEntity>() where TDerivedEntity : class, TEntity
		{
			var entity = _set.Create<TDerivedEntity>();

			InitializeEntity(entity);

			return entity;
		}

		public TEntity Create()
		{
			var entity = _set.Create();

			InitializeEntity(entity);

			return entity;
		}

		public TEntity Find(params object[] keyValues)
		{
			var entity = _set.Find(keyValues);

			if (entity == null)
			{
				return null;
			}

			ThrowIfEntityDoesNotMatchFilter(entity);

			return entity;
		}

		IEnumerator<TEntity> IEnumerable<TEntity>.GetEnumerator()
		{
			return Filtered.GetEnumerator();
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return Filtered.GetEnumerator();
		}

		IList IListSource.GetList()
		{
			throw new InvalidOperationException();
		}

		private void InitializeEntity(TEntity entity)
		{
			if (_initializeEntity != null)
			{
				_initializeEntity(entity);
			}
		}

		public TEntity Remove(TEntity entity)
		{
			ThrowIfEntityDoesNotMatchFilter(entity);

			return _set.Remove(entity);
		}

		public void ThrowIfEntityDoesNotMatchFilter(TEntity entity)
		{
			if (!MatchesFilter(entity))
			{
				throw new ArgumentOutOfRangeException();
			}
		}

		public IQueryable<TEntity> Unfiltered()
		{
			return _set;
		}
	}
}